/*
REWRITTEN BY XINEF
*/

#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "ScriptedGossip.h"
#include "the_black_morass.h"
#include "MoveSplineInit.h"

enum medivhSays
{
    SAY_WEAK75					= 0,
    SAY_WEAK50					= 1,
    SAY_WEAK25					= 2,
    SAY_ENTER					= 3,
    SAY_INTRO					= 4,
    SAY_DEATH					= 5,
    SAY_WIN						= 6,
    SAY_ORCS_ENTER				= 7,

	SAY_ORCS_ANSWER				= 0
};

enum medivhSpells
{
	SPELL_MANA_SHIELD			= 31635,
    SPELL_MEDIVH_CHANNEL		= 31556,
    SPELL_BLACK_CRYSTAL			= 32563,
    SPELL_PORTAL_CRYSTALS		= 32564,
    SPELL_BANISH_PURPLE			= 32566,
    SPELL_BANISH_GREEN			= 32567,

    SPELL_CORRUPT				= 31326,
    SPELL_CORRUPT_AEONUS		= 37853,
};

enum medivhMisc
{
	NPC_DP_EMITTER_STALKER		= 18582,
	NPC_DP_CRYSTAL_STALKER		= 18553,
	NPC_SHADOW_COUNCIL_ENFORCER	= 17023,
	GO_DARK_PORTAL				= 185103,

	EVENT_CHECK_HEALTH_25		= 1,
	EVENT_CHECK_HEALTH_50		= 2,
	EVENT_CHECK_HEALTH_75		= 3,
	EVENT_SUMMON_CRYSTAL		= 4,
	EVENT_SUMMON_FLYING_CRYSTAL	= 5,

	EVENT_OUTRO_1				= 10,
	EVENT_OUTRO_2				= 11,
	EVENT_OUTRO_3				= 12,
	EVENT_OUTRO_4				= 13,
	EVENT_OUTRO_5				= 14,
	EVENT_OUTRO_6				= 15,
	EVENT_OUTRO_7				= 16,
	EVENT_OUTRO_8				= 17
};

class NpcRunToHome : public BasicEvent
{
public:
	NpcRunToHome(Creature& owner) : _owner(owner) { }

	bool Execute(uint64 /*eventTime*/, uint32 /*updateTime*/)
	{
		_owner.GetMotionMaster()->MoveTargetedHome();
		return true;
	}

private:
	Creature& _owner;
};

class npc_medivh_bm : public CreatureScript
{
public:
    npc_medivh_bm() : CreatureScript("npc_medivh_bm") { }

    CreatureAI* GetAI(Creature* creature) const
    {
        return new npc_medivh_bmAI(creature);
    }

    struct npc_medivh_bmAI : public ScriptedAI
    {
        npc_medivh_bmAI(Creature* creature) : ScriptedAI(creature)
        {
            instance = creature->GetInstanceScript();

			groundArray.clear();
			airArray.clear();

			groundArray.push_back(G3D::Vector3(creature->GetPositionX() + 8.0f, creature->GetPositionY(), creature->GetPositionZ()));
			airArray.push_back(G3D::Vector3(creature->GetPositionX(), creature->GetPositionY(), creature->GetPositionZ()));
			for (uint8 i = 0; i < 10; ++i)
				groundArray.push_back(G3D::Vector3(creature->GetPositionX() + 8.0f*cos(2.0f*M_PI*i/10.0f), creature->GetPositionY() + 8.0f*sin(2.0f*M_PI*i/10.0f), creature->GetPositionZ()));

			for (uint8 i = 0; i < 40; ++i)
				airArray.push_back(G3D::Vector3(creature->GetPositionX() + i*0.25f*cos(2.0f*M_PI*i/10.0f), creature->GetPositionY() + i*0.25f*sin(2.0f*M_PI*i/10.0f), creature->GetPositionZ() + i/4.0f));
			for (uint8 i = 40; i < 80; ++i)
				airArray.push_back(G3D::Vector3(creature->GetPositionX() + 10.0f*cos(2.0f*M_PI*i/10.0f), creature->GetPositionY() + 10.0f*sin(2.0f*M_PI*i/10.0f), creature->GetPositionZ() + i/4.0f));
        }

        InstanceScript* instance;
		EventMap events;
		Movement::PointsArray groundArray;
		Movement::PointsArray airArray;

        void Reset()
        {
			events.Reset();
			me->CastSpell(me, SPELL_MANA_SHIELD, true);

			if (instance->GetData(TYPE_AEONUS) != DONE)
				me->CastSpell(me, SPELL_MEDIVH_CHANNEL, false);
        }

		void JustSummoned(Creature* summon)
		{
			instance->SetData64(DATA_SUMMONED_NPC, summon->GetGUID());
			if (summon->GetEntry() == NPC_DP_CRYSTAL_STALKER)
			{
				summon->DespawnOrUnsummon(25000);
				summon->CastSpell(summon, RAND(SPELL_BANISH_PURPLE, SPELL_BANISH_GREEN), true);
				summon->GetMotionMaster()->MoveSplinePath(&airArray);
			}
			else if (summon->GetEntry() == NPC_DP_EMITTER_STALKER)
			{
				summon->CastSpell(summon, SPELL_BLACK_CRYSTAL, true);
				Movement::MoveSplineInit init(summon);
				init.MovebyPath(groundArray);
				init.SetCyclic();
			    init.Launch();
			}
		}

		void SummonedCreatureDespawn(Creature* summon)
		{
			instance->SetData64(DATA_DELETED_NPC, summon->GetGUID());
		}

        void MoveInLineOfSight(Unit* who)
        {
			if (!events.Empty() || instance->GetData(TYPE_AEONUS) == DONE)
				return;

            if (who->GetTypeId() == TYPEID_PLAYER && me->IsWithinDistInMap(who, 20.0f))
            {
                Talk(SAY_ENTER);
				instance->SetData(DATA_MEDIVH, 1);
				me->CastSpell(me, SPELL_MEDIVH_CHANNEL, false);

				events.ScheduleEvent(EVENT_CHECK_HEALTH_75, 500);
				events.ScheduleEvent(EVENT_CHECK_HEALTH_50, 500);
				events.ScheduleEvent(EVENT_CHECK_HEALTH_25, 500);
				events.ScheduleEvent(EVENT_SUMMON_CRYSTAL, 2000);
				events.ScheduleEvent(EVENT_SUMMON_CRYSTAL, 4000);
				events.ScheduleEvent(EVENT_SUMMON_CRYSTAL, 6000);
				events.ScheduleEvent(EVENT_SUMMON_FLYING_CRYSTAL, 8000);
            }
        }

        void AttackStart(Unit* ) { }

		void DoAction(int32 param)
		{
			if (param == ACTION_OUTRO)
			{
				events.Reset();
				events.ScheduleEvent(EVENT_OUTRO_1, 4000);
				me->InterruptNonMeleeSpells(true);

				me->SummonGameObject(GO_DARK_PORTAL, -2086.0f, 7125.6215f, 30.5f, 6.148f, 0.0f, 0.0f, 0.0f, 0.0f, 0);
			}
		}

        void JustDied(Unit* )
        {
			me->SetRespawnTime(DAY);
			events.Reset();
			Talk(SAY_DEATH);
        }

        void UpdateAI(uint32 diff)
        {
			events.Update(diff);
			switch (uint32 eventId = events.ExecuteEvent())
			{
				case EVENT_CHECK_HEALTH_25:
				case EVENT_CHECK_HEALTH_50:
				case EVENT_CHECK_HEALTH_75:
					if (instance->GetData(DATA_SHIELD_PERCENT) <= eventId*25)
					{
						Talk(eventId-1);
						break;
					}
					events.ScheduleEvent(eventId, 500);
					break;
				case EVENT_SUMMON_CRYSTAL:
					me->SummonCreature(NPC_DP_EMITTER_STALKER, me->GetPositionX() + 8.0f, me->GetPositionY(), me->GetPositionZ());
					break;
				case EVENT_SUMMON_FLYING_CRYSTAL:
					me->CastSpell(me, SPELL_PORTAL_CRYSTALS, true);
					events.ScheduleEvent(EVENT_SUMMON_FLYING_CRYSTAL, 1000);
					break;
				case EVENT_OUTRO_1:
					me->SetFacingTo(6.21f);
					Talk(SAY_WIN);
					events.ScheduleEvent(EVENT_OUTRO_2, 17000);
					break;
				case EVENT_OUTRO_2:
					me->SetFacingTo(3.07f);
					events.ScheduleEvent(EVENT_OUTRO_3, 2000);
					break;
				case EVENT_OUTRO_3:
					SummonOrcs(-2046.158f, -3.0f, 37000, 30000, true);
					events.ScheduleEvent(EVENT_OUTRO_4, 2000);
					break;
				case EVENT_OUTRO_4:
					SummonOrcs(-2055.97f, -2.0f, 33000, 28000, false);
					events.ScheduleEvent(EVENT_OUTRO_5, 2000);
					break;
				case EVENT_OUTRO_5:
					SummonOrcs(-2064.0f, -1.5f, 29000, 26000, false);
					events.ScheduleEvent(EVENT_OUTRO_6, 2000);
					break;
				case EVENT_OUTRO_6:
					SummonOrcs(-2074.35f, -0.1f, 26000, 24000, false);
					events.ScheduleEvent(EVENT_OUTRO_7, 7000);
					break;
				case EVENT_OUTRO_7:
					Talk(SAY_ORCS_ENTER);
					events.ScheduleEvent(EVENT_OUTRO_8, 7000);
					break;
				case EVENT_OUTRO_8:
					if (Creature* cr = me->FindNearestCreature(NPC_SHADOW_COUNCIL_ENFORCER, 20.0f))
					{
						cr->SetFacingTo(3.07f);
						cr->AI()->Talk(SAY_ORCS_ANSWER);
					}
					break;

					

			}
        }

		void SummonOrcs(float x, float y, uint32 duration, uint32 homeTime, bool first)
		{
			for (uint8 i = 0; i < 6; ++i)
			{
				if (Creature* cr = me->SummonCreature(NPC_SHADOW_COUNCIL_ENFORCER, -2091.731f, 7133.083f - 3.0f*i, 34.589f, 0.0f))
				{
					cr->GetMotionMaster()->MovePoint(0, (first && i == 3) ? x+2.0f : x, cr->GetPositionY()+y, cr->GetMap()->GetHeight(x, cr->GetPositionY()+y, MAX_HEIGHT, true));
					cr->m_Events.AddEvent(new NpcRunToHome(*cr), cr->m_Events.CalculateTime(homeTime+urand(0, 2000)));
					cr->DespawnOrUnsummon(duration+urand(0, 2000));
				}
			}
		}
    };
};


enum timeRift
{
	EVENT_SUMMON_AT_RIFT		= 1,
	EVENT_CHECK_DEATH			= 2
};

class npc_time_rift : public CreatureScript
{
public:
    npc_time_rift() : CreatureScript("npc_time_rift") { }

    CreatureAI* GetAI(Creature* creature) const
    {
        return new npc_time_riftAI(creature);
    }

    struct npc_time_riftAI : public NullCreatureAI
    {
        npc_time_riftAI(Creature* creature) : NullCreatureAI(creature)
		{
			instance = creature->GetInstanceScript();
			riftKeeperGUID = 0;
		}

		EventMap events;
		InstanceScript* instance;
		uint64 riftKeeperGUID;

        void Reset()
        {
			if (instance->GetData(DATA_RIFT_NUMBER) >= 18)
			{
				me->DespawnOrUnsummon(30000);
				return;
			}

			events.ScheduleEvent(EVENT_SUMMON_AT_RIFT, 16000);
			events.ScheduleEvent(EVENT_CHECK_DEATH, 8000);
        }

		void SetGUID(uint64 guid, int32)
		{
			riftKeeperGUID = guid;
		}

        void DoSummonAtRift(uint32 entry)
        {
            Position pos;
            me->GetNearPosition(pos, 10.0f, 2*M_PI*rand_norm());

			if (Creature* summon = me->SummonCreature(entry, pos, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 15000))
                if (Unit* medivh = ObjectAccessor::GetUnit(*me, instance->GetData64(DATA_MEDIVH)))
				{
					float o = medivh->GetAngle(summon)+frand(-1.0f, 1.0f);
					summon->SetHomePosition(medivh->GetPositionX() + 14.0f*cos(o), medivh->GetPositionY() + 14.0f*sin(o), medivh->GetPositionZ(), summon->GetAngle(medivh));
					summon->GetMotionMaster()->MoveTargetedHome();
					summon->SetReactState(REACT_DEFENSIVE);
				}
        }

        void DoSelectSummon()
        {
            uint32 entry = RAND(NPC_INFINITE_ASSASIN, NPC_INFINITE_WHELP, NPC_INFINITE_CRONOMANCER, NPC_INFINITE_EXECUTIONER, NPC_INFINITE_VANQUISHER);
            if (entry == NPC_INFINITE_WHELP)
            {
				DoSummonAtRift(entry);
				DoSummonAtRift(entry);
				DoSummonAtRift(entry);
            }
			else
				DoSummonAtRift(entry);
        }

        void UpdateAI(uint32 diff)
        {
			events.Update(diff);
			switch (events.ExecuteEvent())
			{
				case EVENT_SUMMON_AT_RIFT:
					DoSelectSummon();
					events.ScheduleEvent(EVENT_SUMMON_AT_RIFT, 15000);
					break;
				case EVENT_CHECK_DEATH:
					if (!me->HasUnitState(UNIT_STATE_CASTING))
					{
						Creature* riftKeeper = ObjectAccessor::GetCreature(*me, riftKeeperGUID);
						if (!riftKeeper || !riftKeeper->IsAlive())
						{
							instance->SetData(DATA_RIFT_KILLED, 1);
							me->DespawnOrUnsummon(0);
							break;
						}
						else
							me->CastSpell(riftKeeper, SPELL_RIFT_CHANNEL, false);
					}
					events.ScheduleEvent(EVENT_CHECK_DEATH, 500);
					break;
			}
        }
    };
};

class spell_black_morass_corrupt_medivh : public SpellScriptLoader
{
	public:
		spell_black_morass_corrupt_medivh() : SpellScriptLoader("spell_black_morass_corrupt_medivh") { }

		class spell_black_morass_corrupt_medivh_AuraScript : public AuraScript
		{
			PrepareAuraScript(spell_black_morass_corrupt_medivh_AuraScript);

			void PeriodicTick(AuraEffect const* /*aurEff*/)
			{
				if (InstanceScript* instance = GetUnitOwner()->GetInstanceScript())
					instance->SetData(DATA_DAMAGE_SHIELD, 1);
			}

			void Register()
			{
				OnEffectPeriodic += AuraEffectPeriodicFn(spell_black_morass_corrupt_medivh_AuraScript::PeriodicTick, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
			}

			InstanceScript* _instance;
		};

		AuraScript* GetAuraScript() const
		{
			return new spell_black_morass_corrupt_medivh_AuraScript();
		}
};

void AddSC_the_black_morass()
{
    new npc_medivh_bm();
    new npc_time_rift();
	new spell_black_morass_corrupt_medivh();
}
